package ewelinkapi

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"time"

	"gitee.com/libs_2/ewelinkapi/constant"
	"gitee.com/libs_2/ewelinkapi/model"
	"gitee.com/libs_2/ewelinkapi/utils"
	"github.com/gorilla/websocket"
	"github.com/rs/zerolog/log"
)

// 查询长链接信息
func (c *Client) GetDispatchInfo(ctx context.Context) (model.DispatchResponse, error) {
	var dr model.DispatchResponse
	res, err := http.Get(c.dispatchDomain)
	if err != nil {
		return dr, err
	}

	defer res.Body.Close()

	decoder := json.NewDecoder(res.Body)
	err = decoder.Decode(&dr)
	if err != nil {
		return dr, err
	}

	return dr, nil
}

// websocket 握手
func (c *Client) Handshake(ctx context.Context, interrupt <-chan os.Signal, dispath model.DispatchResponse, apiKey string) error {
	token, err := c.getToken(ctx)
	if err != nil {
		return err
	}
	req := model.HandshakeRequest{
		Action:      "userOnline",
		AccessToken: token,
		Apikey:      apiKey,
		Appid:       c.appId,
		Nonce:       utils.BuildNonce(),
		Ts:          time.Now().Unix(),
		UserAgent:   "app",
		Sequence:    time.Now().UnixMilli(),
		Version:     8,
	}
	u := url.URL{Scheme: "wss", Host: fmt.Sprintf("%s:%d", dispath.Domain, dispath.Port), Path: "/api/ws"}

	conn, _, err := websocket.DefaultDialer.DialContext(ctx, u.String(), nil)
	if err != nil {
		return err
	}

	err = conn.WriteJSON(req)
	if err != nil {
		return err
	}
	_, message, err := conn.ReadMessage()
	if err != nil {
		return err
	}

	var hr model.HandshakeResponse
	err = json.Unmarshal(message, &hr)
	if err != nil {
		return err
	}

	c.socket.connected = true
	c.socket.conn = conn
	c.socket.config = hr.Config

	done := make(chan struct{})

	go func() {
		defer close(done)
		for {
			mt, message, err := c.socket.conn.ReadMessage()
			if err != nil {
				log.Error().Err(err).Msg("read msg from ewelink socket failed,will close socket")
				close(done)
				c.socket.connected = false
				c.onConnectionClose(interrupt)
				return
			}

			if mt == websocket.CloseMessage {
				close(done)
				c.socket.connected = false
				c.onConnectionClose(interrupt)
				return
			} else if mt == websocket.PongMessage {
				log.Info().Msg("socket:pong")
				continue
			}

			c.onDeviceEvent(message)
		}
	}()

	go c.heartbeat(done, interrupt)

	return nil
}

func (c *Client) ReConnect(ctx context.Context, interrupt <-chan os.Signal, apiKey string) error {
	dr, err := c.GetDispatchInfo(ctx)
	if err != nil {
		return err
	}

	return c.Handshake(ctx, interrupt, dr, apiKey)
}

func (c *Client) GetSocketIsRun() bool {
	return c.socket.connected
}

// 心跳
func (c *Client) heartbeat(done <-chan struct{}, interrupt <-chan os.Signal) {
	hbInterval := c.socket.config.HbInterval - 10
	if c.socket.config.HB == 0 {
		hbInterval = 90
	}
	ticker := time.NewTicker(time.Duration(hbInterval) * time.Second)
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			log.Debug().Msg("send ping ...")
			// err := c.socket.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(5*time.Second))
			err := c.socket.conn.WriteMessage(websocket.TextMessage, []byte("ping"))
			if err != nil {
				log.Error().Err(err).Msg("write ping to ewelink socket failed")
				return
			}
		case <-interrupt:
			log.Warn().Msg("interrupt ewelink socket will be closed")
			// Cleanly close the connection by sending a close message and then
			// waiting (with timeout) for the server to close the connection.
			c.socket.autoConnect = false
			err := c.socket.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Error().Err(err).Msg("write close msg to ewelink socket failed")
				return
			}
			select {
			case <-done:
			case <-time.After(time.Second):
			}
			return
		}
	}
}

// 接收设备消息
func (c *Client) onDeviceEvent(body []byte) {
	if len(body) == 4 { // 判断是不是pong
		if string(body) == "pong" {
			log.Debug().Msg("receive msg:pong")
			return
		}
	}
	var m map[string]interface{}
	err := json.Unmarshal(body, &m)
	if err != nil {
		log.Error().Err(err).Str("body", string(body)).Msg("onDeviceEvent Unmarshal failed")
		return
	}

	action, ok := m["action"].(string)
	if !ok {
		// 现在发现主动查询状态时，没有返回操作，所以只能尝试默认为查询设备状态事件
		// log.Error().Str("body", string(body)).Msg("onDeviceEvent get action failed")
		// return
		action = constant.NOTIFY_ACTION_UPDATE
	}
	switch action {
	case constant.NOTIRY_ACTION_SYSMSG:
		var msg model.NotifyDeviceOnline
		err = json.Unmarshal(body, &msg)
		if err != nil {
			log.Error().Err(err).Str("body", string(body)).Msg("onDeviceEvent Unmarshal to NotifyDeviceOnline failed")
			return
		}
		c.OnDeviceOnlineChange(msg)
	case constant.NOTIFY_ACTION_UPDATE:
		var msg model.NotifyOutlet4Update
		err = json.Unmarshal(body, &msg)
		if err != nil {
			log.Error().Err(err).Str("body", string(body)).Msg("onDeviceEvent Unmarshal to NotifyOutlet4Update failed")
			return
		}
		c.OnDeviceOutlet4UpdateState(msg)
	default:
		log.Error().Str("action", action).Str("body", string(body)).Msg("unknown action onDeviceEvent")
	}
}
